/*
 * receive request from fuse and call methods of yfs_client
 *
 * started life as low-level example in the fuse distribution.  we
 * have to use low-level interface in order to get i-numbers.  the
 * high-level interface only gives us complete paths.
 */

#include <fuse_lowlevel.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <set>
#include <arpa/inet.h>
#include <math.h>
#include "yfs_client.h"

int myid;
std::set<fuse_ino_t> inum_set;
yfs_client *yfs;

int id() { 
  return myid;
}

yfs_client::status
getattr(yfs_client::inum inum, struct stat &st)
{
  yfs_client::status ret;

  bzero(&st, sizeof(st));

  st.st_ino = inum;
  printf("getattr %016llx %d\n", inum, yfs->isfile(inum));
  if(yfs->isfile(inum)){
     yfs_client::fileinfo info;
     ret = yfs->getfile(inum, info);
     if(ret != yfs_client::OK)
       return ret;
     st.st_mode = S_IFREG | 0666;
     st.st_nlink = 1;
     st.st_atime = info.atime;
     st.st_mtime = info.mtime;
     st.st_ctime = info.ctime;
     st.st_size = info.size;
     printf("   getattr -> %llu\n", info.size);
   } else {
     yfs_client::dirinfo info;
     ret = yfs->getdir(inum, info);
     if(ret != yfs_client::OK)
       return ret;
     st.st_mode = S_IFDIR | 0777;
     st.st_nlink = 2;
     st.st_atime = info.atime;
     st.st_mtime = info.mtime;
     st.st_ctime = info.ctime;
     printf("   getattr -> %lu %lu %lu\n", info.atime, info.mtime, info.ctime);
   }
   return yfs_client::OK;
}

void
fuseserver_getattr(fuse_req_t req, fuse_ino_t ino,
          struct fuse_file_info *fi)
{
    struct stat st;
    yfs_client::inum inum = ino; // req->in.h.nodeid;
    yfs_client::status ret;

    ret = getattr(inum, st);
    if(ret != yfs_client::OK){
      fuse_reply_err(req, ENOENT);
      return;
    }
    fuse_reply_attr(req, &st, 0);
}

void
fuseserver_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
{
  /* WORK COMPLETED */

  printf("fuseserver_setattr 0x%x\n", to_set);

  /* Confirm that ino points to a file and that we are told to resize it */
  if (yfs->isfile(ino) && (FUSE_SET_ATTR_SIZE & to_set)) {
	  std::string buf;
	  yfs_client::status ret = yfs->get(ino, buf);

	  /* Confirm file exists */
	  if (ret == yfs_client::OK) {
		printf("   fuseserver_setattr set size to %zu\n", attr->st_size);
		struct stat st;

		/* Handle file resizing cases (new size can be less than, equal to, or greater than old size) */
		buf = buf.substr(0, attr->st_size);
		if(attr->st_size > buf.length())
			buf.append(" ", attr->st_size - buf.length());

		/* Update content map */
		yfs->remove(ino);
		yfs->put(ino, buf);

		/* Add aspects to st and return success */
		st = *attr;
		fuse_reply_attr(req, &st, 0);
	  }
  }
  fuse_reply_err(req, ENOSYS);
}

void
fuseserver_read(fuse_req_t req, fuse_ino_t ino, size_t size,
      off_t off, struct fuse_file_info *fi)
{
  /* WORK COMPLETED */

  std::string str;
  yfs->get(ino, str);
  std::string buf = str.substr(off, size); 
  fuse_reply_buf(req, buf.c_str(), size);
  //fuse_reply_err(req, ENOSYS); -- When should we reply with this if ever?
}

void
fuseserver_write(fuse_req_t req, fuse_ino_t ino,
  const char *buf, size_t size, off_t off,
  struct fuse_file_info *fi)
{
  /*IMPLEMENT THIS FUNCTION FOR LAB 3*/

  // You fill this in 

  // Remember that bytes_written may not just be size_t size
  // if off_t off is greater than the file's current size.
  // Make sure to return the number of actual bytes written at 
  // the extent server.

//  int bytes_written = 0;
//  if(off < str.length())
//	bytes_written = off;
//  std::string str;
//  yfs->get(ino, str);
//  int attrSize = str.length();
//  str = str.substr(0, off);
//
//  str.append(buf.substr(0, attrSize - off));
//  bytes_written += attrSize - off;
//
//  yfs->remove(ino);
//  yfs->put(ino, str);
//
//#if 0
//  fuse_reply_write(req, bytes_written);
//#else
//  fuse_reply_err(req, ENOSYS);
//#endif
}

void
fuseserver_open(fuse_req_t req, fuse_ino_t ino,
     struct fuse_file_info *fi)
{
  /* WORK COMPLETED */

  /* Confirm that ino points to a file (not a directory) and that the file exists */
 std::string garb;
 if(yfs->isfile(ino) && (yfs->get(ino, garb) == yfs_client::OK)) {
	 fuse_reply_open(req, fi);
 	 return;
 }
 fuse_reply_err(req, ENOSYS); //enosys = not implemented
}

/* Generate a random inum */
fuse_ino_t makeInum(bool file) {

	/* Bit masks used to flag 32nd bit to signal whether an inum points to a directory or a file */
    long long file_indicator = 1 << 31;
    long long dir_indicator = ~(file_indicator);
    fuse_ino_t rand_inum;

    /* Generate random number, set flag bit and confirm it is unique */
    do {
    	rand_inum = rand();
        if (file)
        	rand_inum = rand_inum | file_indicator;
        else
             rand_inum = rand_inum & dir_indicator;
    }
    while (inum_set.find(rand_inum) != inum_set.end());

    /* Insert inum to set to maintain uniqueness */
    inum_set.insert(rand_inum);
    return rand_inum;
}

int findIno(fuse_ino_t parent, const char* n)
{
  std::string stPar;
  yfs->get(parent, stPar);
  std::string search = " ";
  search.append(std::string(n));
  search.append( " ");
  int i = stPar.find(search);
  return i;
  /*stPar = stPar.substr(i+search.length()+1);
  i = stPar.find(" ");
  return strtoul(stPar.substr(0, i));*/
}

std::string findName(fuse_ino_t parent, fuse_ino_t ino)
{
  std::string stPar;
  yfs->get(parent, stPar);
  char buf[50];
  sprintf(buf, " %lu ", ino);
  int i = stPar.find(std::string(buf));
  stPar = stPar.substr(0, i);
  i = stPar.find_last_of(" ");
  return stPar.substr(i+1);
}

/* fuse_ino_t is an unsigned long */
//yfs_client::status
//fuseserver_createhelper(fuse_ino_t parent, const char *name,
//     mode_t mode, struct fuse_entry_param *e)
//{
//  /*IMPLEMENT THIS FUNCTION FOR LAB 2*/
//  /* This function will only be called for files, not directories,
//     which would use mkdir().  You may safely ignore the "mode"
//     parameter. */
//
////Create inum
//	fuse_ino_t inum = makeInum(1);
//	if(yfs->put(inum, std::string(name)) == yfs_client::OK)
//	{
//		e->ino = inum;
//		e->generation = 0;
//		getattr(inum, e->attr);
//		e->attr_timeout = 0;
//		e->entry_timeout = 0;
//
//		//Modify contents of parent
//
//		std::string parSt;
//		yfs->get(parent, parSt);
//		char buf[50];
//		sprintf(buf, " %s %lu ", name, inum );
//		parSt.append(std::string(buf));
//		yfs->remove(parent);
//		yfs->put(parent, parSt);
//
//		return yfs_client::OK;
//	}
//  // Fill in e before returning.
//  // You may choose 0 for the generation number and timeout entries
//
//  return yfs_client::NOENT;
//}

yfs_client::status
fuseserver_createhelper(fuse_ino_t parent, const char *name,
     mode_t mode, struct fuse_entry_param *e)
{
  struct stat st;
  //generate a suitable ino for a file
  yfs_client::inum ino = ( (random() % (int)pow(2,31)) | 0x80000000);

  yfs_client::fileinfo info;
  yfs_client::status ret;

  //call create file and fill in e
  ret = yfs->createfile(parent, ino, name, info);
  if(ret != yfs_client::OK){
    return ret;
  }
  e->attr_timeout = 0.0;
  e->entry_timeout = 0.0;
  e->generation = 0.0;
  e->ino = ino;
  st.st_ino = ino;
  st.st_mode = S_IFREG | 0666;
  st.st_nlink = 1;
  st.st_atime = info.atime;
  st.st_mtime = info.mtime;
  st.st_ctime = info.ctime;
  st.st_size = info.size;
  e->attr = st;

  return yfs_client::OK;
}



void
fuseserver_create(fuse_req_t req, fuse_ino_t parent, const char *name,
   mode_t mode, struct fuse_file_info *fi)
{
  struct fuse_entry_param e;
  if( fuseserver_createhelper( parent, name, mode, &e ) == yfs_client::OK ) {
    fuse_reply_create(req, &e, fi);
  } else {
    fuse_reply_err(req, ENOENT);
  }
}

void fuseserver_mknod( fuse_req_t req, fuse_ino_t parent, 
    const char *name, mode_t mode, dev_t rdev ) {
  struct fuse_entry_param e;
  if( fuseserver_createhelper( parent, name, mode, &e ) == yfs_client::OK ) {
    fuse_reply_entry(req, &e);
  } else {
    fuse_reply_err(req, ENOENT);
  }
}

void
fuseserver_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
{
  /*IMPLEMENT THIS FUNCTION FOR LAB 2*/

  struct fuse_entry_param e;
  bool found = false;

  e.attr_timeout = 0.0;
  e.entry_timeout = 0.0;

  // You fill this in:
  // Look up the file named `name' in the directory referred to by
  // `parent' in YFS. If the file was found, initialize e.ino and
  // e.attr appropriately.
  found = findIno(parent, name) != -1;

  if (found)
    fuse_reply_entry(req, &e);
  else
    fuse_reply_err(req, ENOENT);
}


struct dirbuf {
    char *p;
    size_t size;
};

void dirbuf_add(struct dirbuf *b, const char *name, fuse_ino_t ino)
{
    struct stat stbuf;
    size_t oldsize = b->size;
    b->size += fuse_dirent_size(strlen(name));
    b->p = (char *) realloc(b->p, b->size);
    memset(&stbuf, 0, sizeof(stbuf));
    stbuf.st_ino = ino;
    fuse_add_dirent(b->p + oldsize, name, &stbuf, b->size);
}

#define min(x, y) ((x) < (y) ? (x) : (y))

int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
          off_t off, size_t maxsize)
{
  if (off < bufsize)
    return fuse_reply_buf(req, buf + off, min(bufsize - off, maxsize));
  else
    return fuse_reply_buf(req, NULL, 0);
}

void
fuseserver_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
          off_t off, struct fuse_file_info *fi)
{
  /*IMPLEMENT THIS FUNCTION FOR LAB 2*/

  yfs_client::inum inum = ino; // req->in.h.nodeid;
  struct dirbuf b;
  yfs_client::dirent e;

  printf("fuseserver_readdir\n");

 if(!yfs->isdir(inum)){
    fuse_reply_err(req, ENOTDIR);
    return;
  }

  memset(&b, 0, sizeof(b));


   // fill in the b data structure using dirbuf_add
   std::string data;
   yfs->get(ino, data);
   
   while(data != "")
   {
	int i = data.find("  ");
	data = data.substr(i+2);
	i = data.find(" ");
	std::string name = data.substr(0, i);
	dirbuf_add(&b, name.c_str(), ino);
	data = data.substr(i+1);
   }
   
   reply_buf_limited(req, b.p, b.size, off, size);
   free(b.p);
}

void
fuseserver_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
     mode_t mode)
{
  /*IMPLEMENT THIS FUNCTION FOR LAB 4*/

  struct fuse_entry_param e;

  // You fill this in
#if 0
  fuse_reply_entry(req, &e);
#else
  fuse_reply_err(req, ENOSYS);
#endif
}

void
fuseserver_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
{
  /*IMPLEMENT THIS FUNCTION FOR LAB 4*/

  // You fill this in
  // Success:	fuse_reply_err(req, 0);
  // Not found:	fuse_reply_err(req, ENOENT);
  fuse_reply_err(req, ENOSYS);
}

void
fuseserver_statfs(fuse_req_t req)
{
  struct statvfs buf;

  printf("statfs\n");

  memset(&buf, 0, sizeof(buf));

  buf.f_namemax = 255;
  buf.f_bsize = 512;

  fuse_reply_statfs(req, &buf);
}

struct fuse_lowlevel_ops fuseserver_oper;

int
main(int argc, char *argv[])
{
  char *mountpoint = 0;
  int err = -1;
  int fd;
  inum_set.insert(0x1);

  setvbuf(stdout, NULL, _IONBF, 0);

  if(argc != 4){
    fprintf(stderr, "Usage: yfs_client <mountpoint> <port-extent-server> <port-lock-server>\n");
    exit(1);
  }
  mountpoint = argv[1];

  srandom(getpid());

  myid = random();

  yfs = new yfs_client(argv[2], argv[3]);

  fuseserver_oper.getattr    = fuseserver_getattr;
  fuseserver_oper.statfs     = fuseserver_statfs;
  fuseserver_oper.readdir    = fuseserver_readdir;
  fuseserver_oper.lookup     = fuseserver_lookup;
  fuseserver_oper.create     = fuseserver_create;
  fuseserver_oper.mknod      = fuseserver_mknod;
  
  /* Uncomment these 4 lines for LAB 3 */
  fuseserver_oper.open       = fuseserver_open;
  fuseserver_oper.read       = fuseserver_read;
  fuseserver_oper.write      = fuseserver_write;
  fuseserver_oper.setattr    = fuseserver_setattr;
  
  /* Uncomment these 4 lines for LAB 4 */
  //fuseserver_oper.unlink     = fuseserver_unlink;
  //fuseserver_oper.mkdir      = fuseserver_mkdir;

  const char *fuse_argv[20];
  int fuse_argc = 0;
  fuse_argv[fuse_argc++] = argv[0];
#ifdef __APPLE__
  fuse_argv[fuse_argc++] = "-o";
  fuse_argv[fuse_argc++] = "nolocalcaches"; // no dir entry caching
  fuse_argv[fuse_argc++] = "-o";
  fuse_argv[fuse_argc++] = "daemon_timeout=86400";
#endif

  // everyone can play, why not?
  //fuse_argv[fuse_argc++] = "-o";
  //fuse_argv[fuse_argc++] = "allow_other";

  fuse_argv[fuse_argc++] = mountpoint;
  fuse_argv[fuse_argc++] = "-d";

  fuse_args args = FUSE_ARGS_INIT( fuse_argc, (char **) fuse_argv );
  int foreground;
  int res = fuse_parse_cmdline( &args, &mountpoint, 0 /*multithreaded*/, 
        &foreground );
  if( res == -1 ) {
    fprintf(stderr, "fuse_parse_cmdline failed\n");
    return 0;
  }
  
  args.allocated = 0;

  fd = fuse_mount(mountpoint, &args);
  if(fd == -1){
    fprintf(stderr, "fuse_mount failed\n");
    exit(1);
  }

  struct fuse_session *se;

  se = fuse_lowlevel_new(&args, &fuseserver_oper, sizeof(fuseserver_oper),
       NULL);
  if(se == 0){
    fprintf(stderr, "fuse_lowlevel_new failed\n");
    exit(1);
  }

  struct fuse_chan *ch = fuse_kern_chan_new(fd);
  if (ch == NULL) {
    fprintf(stderr, "fuse_kern_chan_new failed\n");
    exit(1);
  }

  fuse_session_add_chan(se, ch);
  // err = fuse_session_loop_mt(se);   // FK: wheelfs does this; why?
  err = fuse_session_loop(se);
    
  fuse_session_destroy(se);
  close(fd);
  fuse_unmount(mountpoint);

  return err ? 1 : 0;
}
